
import { cubicIn, cubicOut } from '../../../common/animation/index';
import { parseColorText } from '../../../common/color/color';
import { loadImage, IndexImage, IndexImagePainter } from '../../../common/image/index';

import layout from './layout';

/**
 * @returns {Promise<NaviMap>}
 */
function load(context) {
  if (mapCache.image) {
    return mapCache.image instanceof Promise ?
        mapCache.image.then(() => mapCache) : Promise.resolve(mapCache);
  }

  /** @type {IndexImagePainter} */
  let painter;
  const detailUrl = context.assetPath('assets/navi/map_details.json');
  return mapCache.image = fetch(detailUrl)
    .then(d => d.json())
    .then(d => {
      const url = detailUrl.substring(0, detailUrl.lastIndexOf('/')) + '/' + d.image;
      mapCache.initPt = d.initPt;
      mapCache.interestPts = d.interestPts;
      return loadImage(url);
    })
    .then(data => {
      const img = new IndexImage(data);
      painter = img.createPainter();
      return new Promise((rs) => setTimeout(() => rs())); // 强制让出 CPU 时间, 下同
    })
    .then(_ => {
      mapCache.pathD3 = painter.toPath(c => c.a > 0).path;
      return new Promise((rs) => setTimeout(() => rs()));
    })
    .then(_ => {
      painter.palette = painter.palette.map(c => {
        if (c.a == 0) return c;
        if (c.r > 250) return parseColorText(layout.colors.strokeBgL1);
        if (c.r > 150) return parseColorText(layout.colors.strokeBg);
        if (c.r > 90) return parseColorText(layout.colors.strokeBgD1);
        return parseColorText(layout.colors.strokeBgD3);
      });
      mapCache.convertImage = painter.to2dCanvas();
      mapCache.image = painter.img;
      return mapCache;
    });
}

class NaviMap {
  /** @type {Promise<any> | IndexImage} */
  image = null;
  pathD3 = ''; // 相当于描边
  path = '';
  /** @type {HTMLCanvasElement} */
  convertImage = null;

  initPt = {x:0, y:0};
  /** @type {{x:number, y:number, name:string}[]} */
  interestPts = [];
}

const mapCache = new NaviMap();

class Cursor {
  viewWidth = 0;
  viewHeight = 0;

  imgWidth = 0; // 已经计算了 imgScale 影响
  imgHeight = 0; // 已经计算了 imgScale 影响
  imgScale = 1.5;

  cursorX = 0;
  cursorY = 0;
  /** @type {{x:number, y:number, name:string}[]} 已经计算了 imgScale 影响 */
  interestPts = [];

  /** @type {((cursor:Cursor)=>void)[]} */
  onCursorMoved = [];

  // 计算得到的值 - 辅助计算用
  baseX = 0;
  baseY = 0;
  deltaX = 0;
  deltaY = 0;

  // 计算得到的值 - 而且是终值
  dirty = true;
  viewX = 0;
  viewY = 0;

  /**
   * @param {IndexImage} img
   * @param {number} w view 的宽
   * @param {number} h view 的高
   */
  setImageAndView(img, w, h) {
    const realImgW = this.imgWidth = (img.width * this.imgScale) | 0;
    const realImgH = this.imgHeight = (img.height * this.imgScale) | 0;
    this.viewWidth = w;
    this.viewHeight = h;

    if (w >= realImgW) { // 固定位置
      this.deltaX = 0;
      this.baseX = ((w - realImgW) / 2) | 0;
    } else { // 移动位置, deltaX < 0
      this.baseX = 0;
      this.deltaX = (w - realImgW) / realImgW;
    }

    if (h >= realImgH) { // 固定位置
      this.deltaY = 0;
      this.baseY = ((h - realImgH) / 2) | 0;
    } else { // 移动位置
      this.baseY = 0;
      this.deltaY = (h - realImgH) / realImgH;
    }
  }

  /** @param {{x:number, y:number, name:string}[]} pts */
  setInterestPts(pts) {
    const { imgScale } = this;
    this.interestPts = pts.map(pt => {
      return {
        name: pt.name,
        x: imgScale * pt.x,
        y: imgScale * pt.y,
      };
    })
  }

  /**
   * 当前指针位置, 已经计算了 imageScale 影响.
   */
  setCursor(x = this.cursorX, y = this.cursorY) {
    this.cursorX = x;
    this.cursorY = y;
    this.dirty = true;
    this.onCursorMoved.forEach(f => f(this));
  }

  /**
   * 用当前 view (屏幕能显示的区域)大小, image 大小+缩放, 当前指针位置,
   * 计算 viewX 和 viewY.
   * 
   * viewX 和 viewY 就是这个地图左上角点在整个 view 的位置.
   * 因为 image 缩放后, 整体看起来比 view 的范围还要大,
   * 这就导致 view 只能显示 image 的一部分.
   * 
   * 当视角转变时, 需要根据 cursor 的位置动态调整 view 的位置,
   * 使: cursor 一直在 view 区域内可见.
   */
  calcViewPosition() {
    if (!this.dirty) return;

    this.viewX = this.baseX + this.cursorX * this.deltaX;
    this.viewY = this.baseY + this.cursorY * this.deltaY;
    this.dirty = false;
  }

  /** @type {{x:number, y:number, name:string}[]} 已经计算了 imgScale 影响 */
  selectPts = [];
  currentIndex = 0;

  /**
   * 选择下次光标移动到的位置
   */
  prepareNextMove() {
    const { selectPts } = this;

    if (selectPts.length) {
      this.currentIndex++;
      if (this.currentIndex >= selectPts.length) {
        this.currentIndex = 0;
      }
    } else {
      selectPts.push(...(this.interestPts.sort(() => Math.random() - 0.5)));
    }
    return selectPts[this.currentIndex];
  }

}

class Meter {
  /**
   * @param {Cursor} cursor 
   */
  constructor(cursor) {
    this.cursor = cursor;
    
    this.position = 0;
    /** @type {number[]} */
    this.meters = new Array(41).fill(0);
    this.dirty = true;
    cursor.onCursorMoved.push(() => this.dirty = true);
  }

  /**
   * @returns 数值是否变动
   */
  calc() {
    if (this.dirty == false) return false;

    const {cursor, meters} = this;
    const {cursorX, cursorY, interestPts:pts} = cursor;

    this.position = this.calcPos();
    const centerIndex = (meters.length / 2) | 0;
    const distanceToV = (d) => d > 70 ? 0 :
        d >= 20 ? cubicIn(1 - (d - 20) / 50) * 0.5 : cubicOut(1 - d / 20) * 0.2 + 0.5;

    meters.forEach((_, index, array) => {
      const offset = (index - centerIndex) * 5;
      const x = cursorX + offset * 0.7;
      const y = cursorY + offset * 0.3;

      let v = pts.reduce((p, c) => p + distanceToV(Math.hypot(c.x - x, c.y - y)), 0.1);
      array[index] = v > 1 ? 1 : v;
    });
    this.dirty = false;
    return true;
  }

  calcPos(x = this.cursor.cursorX, y = this.cursor.cursorY) {
    return x * 0.7 + y * 0.3;
  }
}

export {
  load, NaviMap, Cursor, Meter,
};
